/***************************************************************************
 *   Copyright (C) 2009 by Kbranch                                         *
 *   kbranch@andLinux                                                      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "stats.h"

const RatingsConstants Stats::ratingsConstants = {
	2522.306882307692307692, // Haste rating required for 1% haste
	1231.6239928, // Armor penetration rating required for 1% armor penetration
	4590.598679, // Crit rating required for a 1% higher chance to crit
	3278.998947, // Hit rating required for a 1% higher chance to hit
	3278.998947, // Expertise rating require dfor a 1% lower chance to be dodged
	8333.333333, // Amount of agility required for 1% crit
};

Stats::Stats()
{
	for(int i = 0; i <= NUMSTATS; i++)
		stats[i] = 0;
	
	for(int i = 0; i <= NUMMODS; i++)
		modifiers[i] = 1;
	
	baseMissChance = 0.08;
	baseDodgeChance = 0.065;
	baseAttackSpeed = 10000000;
	baseBossArmor = 10643;
	
	this->CalculateAll();
}

Stats::Stats(double newStats[], double newMods[], int statsLength, int modsLength, int newBaseMissChance, int newBaseDodgeChance, int64 newBaseAttackSpeed)
{
	for(int i = 0; i < statsLength; i++)
		stats[i] = newStats[i];
	
	for(int i = 0; i < modsLength; i++)
		modifiers[i] = newMods[i];
	
	for(int i = statsLength; i < NUMSTATS; i++)
		stats[i] = 0;
	
	for(int i = modsLength; i < NUMMODS; i++)
		modifiers[i] = 1;
	
	baseMissChance = newBaseMissChance;
	baseDodgeChance = newBaseDodgeChance;
	baseAttackSpeed = newBaseAttackSpeed;
	baseBossArmor = 10643;
	
	this->CalculateAll();
}

Stats& Stats::operator=(const Stats &rightSide)
{
	if(&rightSide == this)
		return *this;
	
	for(int i = 0; i < NUMSTATS; i++)
		stats[i] = rightSide.stats[i];
	
	for(int i = 0; i < NUMMODS; i++)
		modifiers[i] = rightSide.modifiers[i];
	
	this->CalculateAll();
	
	return *this;
}

Stats& Stats::operator+=(const Stats &rightSide)
{
	for(int i = 0; i < NUMMODS; i++)
		modifiers[i] *= rightSide.modifiers[i];
	
	for(int i = 3; i < NUMSTATS; i++)
		stats[i] += rightSide.stats[i];
	
	if(rightSide.stats[BASEAGI] || rightSide.stats[BASESTR] || rightSide.stats[BASEAP])
		this->CalculateStats();
	
	if(rightSide.stats[BASEAGI] || rightSide.stats[CRITRATING] || rightSide.stats[CRITPERCENT])
		this->CalculateCritChance();
	
	if(rightSide.stats[HITRATING] || rightSide.stats[HITPERCENT] || rightSide.stats[EXPERTISERATING] || rightSide.stats[EXPERTISEPERCENT])
		this->CalculateHitChance();
	
	if(rightSide.stats[HASTERATING] || rightSide.modifiers[HASTEMOD] > 1)
		this->CalculateHaste();
	
	if(rightSide.stats[ARPENRATING] || rightSide.modifiers[ARPENMOD] > 1)
		this->CalculateArPen();
	
	return *this;
}

Stats& Stats::operator-=(const Stats &rightSide)
{
	for(int i = 0; i < NUMMODS; i++)
		modifiers[i] /= rightSide.modifiers[i];
	
	for(int i = 3; i < NUMSTATS; i++)
		stats[i] -= rightSide.stats[i];
	
	if(rightSide.stats[BASEAGI] || rightSide.stats[BASESTR] || rightSide.stats[BASEAP])
		this->CalculateStats();
	
	if(rightSide.stats[BASEAGI] || rightSide.stats[CRITRATING] || rightSide.stats[CRITPERCENT])
		this->CalculateCritChance();
	
	if(rightSide.stats[HITRATING] || rightSide.stats[HITPERCENT] || rightSide.stats[EXPERTISERATING] || rightSide.stats[EXPERTISEPERCENT])
		this->CalculateHitChance();
	
	if(rightSide.stats[HASTERATING] || rightSide.modifiers[HASTEMOD] > 1)
		this->CalculateHaste();
	
	if(rightSide.stats[ARPENRATING] || rightSide.modifiers[ARPENMOD] > 1)
		this->CalculateArPen();
	
	return *this;
}

const Stats Stats::operator+(const Stats &rightSide) const
{
	Stats result = *this;
	result += rightSide;
	return result;
}

const Stats Stats::operator-(const Stats &rightSide) const
{
	Stats result = *this;
	result -= rightSide;
	return result;
}

bool Stats::operator==(const Stats &rightSide) const
{
	bool equal = true;
	
	for(int i = 0; i < NUMSTATS; i++)
		if(stats[i] != rightSide.stats[i])
			equal = false;
	
	for(int i = 0; i < NUMMODS; i++)
		if(modifiers[i] != rightSide.modifiers[i])
			equal = false;
	
	if(critChance != rightSide.critChance || hitChance != rightSide.hitChance || baseMissChance != rightSide.baseMissChance || baseDodgeChance != rightSide.baseDodgeChance || attackSpeed != rightSide.attackSpeed || baseAttackSpeed != rightSide.baseAttackSpeed || baseBossArmor != rightSide.baseBossArmor || gearArPenPercent != rightSide.gearArPenPercent || armorDamageReduction != rightSide.armorDamageReduction)
		equal = false;
	
	return equal;
}

bool Stats::operator!=(const Stats &rightSide) const
{
	return !(*this == rightSide);
}

void Stats::CalculateStats()
{
	int tempAgi = 0;
	int tempStr = 0;
	
	if(stats[BASEAGI] > stats[BASESTR])
		tempAgi = stats[HIGHESTSTAT];
	else
		tempStr = stats[HIGHESTSTAT];
	
	stats[AGI] = int((stats[BASEAGI] + tempAgi) * modifiers[STATMOD]);
	stats[STR] = int((stats[BASESTR] + tempStr) * modifiers[STATMOD]);
	stats[AP] = int((stats[BASEAP] + stats[AGI] + stats[STR] * 2) * modifiers[APMOD]);
}

void Stats::CalculateCritChance()
{
	critChance = (stats[CRITRATING] / ratingsConstants.ratingPerCrit + stats[AGI] / ratingsConstants.agiPerCrit) + stats[CRITPERCENT];
	
	if(critChance > 1)
		critChance = 1;
}

void Stats::CalculateHitChance()
{
	double missChance = baseMissChance - stats[HITRATING] / ratingsConstants.ratingPerHit - stats[HITPERCENT];
	double dodgeChance = baseDodgeChance - stats[EXPERTISERATING] / ratingsConstants.ratingPerExpertise - stats[EXPERTISEPERCENT];
	
	if(missChance < 0)
		missChance = 0;
	if(dodgeChance < 0)
		dodgeChance = 0;
	
	hitChance = 1 - missChance - dodgeChance;
}

void Stats::CalculateHaste()
{
	double gearHaste = 1 + stats[HASTERATING] / ratingsConstants.ratingPerHaste;
	attackSpeed = baseAttackSpeed / (gearHaste * modifiers[HASTEMOD]);
}

void Stats::CalculateArPen()
{
	gearArPenPercent = stats[ARPENRATING] / ratingsConstants.ratingPerArPen;
	
	if(gearArPenPercent > 1)
		gearArPenPercent = 1;
	
	double cap = (baseBossArmor * modifiers[ARPENMOD] + 15232.5) / 3;
	
	if(cap > baseBossArmor * modifiers[ARPENMOD])
		cap = baseBossArmor * modifiers[ARPENMOD];
	
	int reducedBossArmor = baseBossArmor * modifiers[ARPENMOD] - gearArPenPercent * cap;
	armorDamageReduction = reducedBossArmor / (reducedBossArmor + 15232.5);
}

void Stats::CalculateAll()
{
	this->CalculateStats();
	this->CalculateCritChance();
	this->CalculateHitChance();
	this->CalculateHaste();
	this->CalculateArPen();
}

double Stats::GetStat(int stat) const
{
	return stats[stat];
}

double Stats::GetModifier(int modifier) const
{
	return modifiers[modifier];
}

double Stats::GetCritChance() const
{
	return critChance;
}

double Stats::GetHitChance() const
{
	return hitChance;
}

int64 Stats::GetAttackSpeed() const
{
	return attackSpeed;
}

double Stats::GetGearArPenPercent() const
{
	return gearArPenPercent;
}

double Stats::GetArmorDamageReduction() const
{
	return armorDamageReduction;
}

void Stats::SetStat(int stat, double value)
{
	stats[stat] = value;
	
	this->CalculateAll();
}

void Stats::SetModifier(int modifier, double value)
{
	modifiers[modifier] = value;
	
	this->CalculateAll();
}

void Stats::SetBaseBossArmor(int newArmor)
{
	baseBossArmor = newArmor;
}

void Stats::AddStat(int stat, double value)
{
	stats[stat] += value;
	
	this->CalculateAll();
}

void Stats::AddModifier(int modifier, double value)
{
	modifiers[modifier] *= value;
	
	this->CalculateAll();
}

void Stats::SubtractStat(int stat, double value)
{
	stats[stat] -= value;
	
	this->CalculateAll();
}

void Stats::SubtractModifier(int modifier, double value)
{
	modifiers[modifier] /= value;
	
	this->CalculateAll();
}
